package com.lhl.mq.mqClient;

import com.lhl.mq.common.*;

import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Connection {
    private Socket socket;

    //一个connection需要管理多个channel，用一个 hash 表把所有的channel管理起来
    private ConcurrentHashMap<String,Channel> channelMap = new ConcurrentHashMap<>();

    private InputStream inputStream;
    private OutputStream outputStream;
    private DataInputStream dataInputStream;
    private DataOutputStream dataOutputStream;

    private ExecutorService callbackPoll = null;


    public Connection(String host,int port) throws IOException {
        socket = new Socket(host,port);
        inputStream = socket.getInputStream();
        outputStream = socket.getOutputStream();
        dataInputStream = new DataInputStream(inputStream);
        dataOutputStream = new DataOutputStream(outputStream);

        callbackPoll = Executors.newFixedThreadPool(4);

        //创建一个扫描线程不停的去扫描socket，读取里面的响应
        Thread t = new Thread(() -> {
            try {
                while (!socket.isClosed()) {
                    Response response = readResponse();
                    //判断这个响应属于哪种响应
                    dispatchReturn(response);
                }
            } catch (SocketException e) {
                //连接正常断开，此时忽略这个异常
                System.out.println("[Connection] 连接正常断开！");
            }catch (IOException | ClassNotFoundException | MqException e){
                //连接异常断开
                e.printStackTrace();
                System.out.println("[Connection] 连接异常断开！");
            }
        });

        t.start();
    }

    public void close(){
        try {
            callbackPoll.shutdownNow();
            channelMap.clear();
            inputStream.close();
            outputStream.close();
            socket.close();
        }catch (IOException e) {
            e.printStackTrace();
        }
    }

    //对响应进行判断
    private void dispatchReturn(Response response) throws IOException, ClassNotFoundException, MqException {
        if (response.getType() == 0xc){
            //属于服务器推送消息的响应
            SubScribeReturns returns = (SubScribeReturns) BinaryTool.fromBytes(response.getPayload());
            //根据channelId去map表里面找channel
            Channel channel = channelMap.get(returns.getChannelId());
            if (channel == null){
                throw new MqException("[Connection] 该消息对应的channel在客户端中不存在，channelId = " + channel.getChannelId());
            }
            //执行channel的回调方法
            //将回调方法放入到线程池里面执行
            callbackPoll.submit(() -> {
                try {
                    channel.getConsumer().handleDelivery(returns.getConsumerTag(),returns.getBasicProperties(),
                            returns.getBody());
                } catch (IOException | ClassNotFoundException | MqException e) {
                    e.printStackTrace();
                    System.out.println("[Connection] 客户端收到消息后执行回调方法时，出现异常!");
                }
            });

        }else {
            //根据客户端的请求给出的响应
            BasicReturns basicReturns = (BasicReturns) BinaryTool.fromBytes(response.getPayload());
            //根据channelId获取channel
            Channel channel = channelMap.get(basicReturns.getChannelId());
            if (channel == null){
                throw new MqException("[Connection] 该消息对应的channel在客户端中不存在，channelId = " + channel.getChannelId());
            }
            //将获取到的响应放入到channel的map表中
            channel.putReturn(basicReturns);
        }
    }

    //发送请求
    public void writeRequest(Request request) throws IOException {
        dataOutputStream.writeInt(request.getType());
        dataOutputStream.writeInt(request.getLength());
        dataOutputStream.write(request.getPayload());
        //刷新缓冲区
        dataOutputStream.flush();
        System.out.println("[Connection] 发送请求，type = " + request.getType() + ", length = " + request.getLength());
    }

    //读取响应
    public Response readResponse() throws IOException {
        Response response = new Response();
        response.setType(dataInputStream.readInt());
        response.setLength(dataInputStream.readInt());
        byte[] payload = new byte[response.getLength()];
        int n = dataInputStream.read(payload);
        if (n != response.getLength()){
            throw new IOException("[Connection] 读取的响应数据不完整！");
        }
        response.setPayload(payload);
        System.out.println("[Connection] 收到响应,type = " + response.getType() + ", length = "
                + response.getLength());
        return response;
    }

    //通过这个方法，在 Connection 中创建出一个 Channel
    public Channel createChannel() throws IOException {
        String channelId = "C-" + UUID.randomUUID();
        Channel channel = new Channel(channelId,this);
        //把 channel 对象放到 Connection 管理的的哈希表中
        channelMap.put(channelId,channel);
        //同时也要把创建 channel 的消息告诉服务器
        //服务器端在将创建channel的请求构造好后
        //会将请求通过Connection发送
        boolean ok = channel.createChannel();

        if (!ok){
            //如果服务器创建channel失败了，则表明这次创建channel操作不顺利
            //需要把刚才已经加入到hash标准的键值对删除掉
            channelMap.remove(channelId);
            return null;
        }
        return channel;
    }
}
